package top.superflow.flow.runtime.route

import top.superflow.asyn.AsynUtils
import top.superflow.exception.Exceptable
import top.superflow.flow.condition.ExpressionEngine
import top.superflow.flow.condition.exl.ExtExpressionCondition
import top.superflow.flow.runtime.Executable
import top.superflow.flow.runtime.flow.{FlowRequest, FlowResponse}
import top.superflow.flow.runtime.inf.IOSchema
import top.superflow.flow.runtime.mapper.{AutoMatchUtils, RuntimeMapper}
import top.superflow.flow.runtime.process.{ExtProcess, ProcessRequest, ProcessResponse, ProcessType}
import top.superflow.lang.{CollectionUtils, StringUtils}
import top.superflow.log.Loggable
import top.superflow.management.api.ConditionProcessConstant

import scala.beans.{BeanProperty, BooleanBeanProperty}
import scala.collection.mutable
import scala.collection.mutable.Set
import scala.concurrent.{ExecutionContext, Future, Promise}

class ExtRoutedNode extends Executable[FlowRequest, FlowResponse] with Loggable with Exceptable{

  @BeanProperty
  var process : ExtProcess = _

  @BeanProperty
  var posts : Set[ExtRoutedNode] = _

  @BeanProperty
  var mapper : RuntimeMapper = _

  @BooleanBeanProperty
  var main : Boolean = false

  @BeanProperty
  var passCondition : ExtExpressionCondition = _

  def execute(request: FlowRequest, response: FlowResponse): Unit = {
    if(! isConditionSuitable(request, response)){
      logger.info("The branch of flow ended at the process {} as the previous result doesn't pass the condition {}", this.process, this.passCondition)
      return
    }

    val processRequest: ProcessRequest = convertToProcessRequest(request, response, process.getInputSchema)
    recordRequest(request, processRequest)
    val processResponse = new ProcessResponse
    process.execute(processRequest,processResponse)
    recordResponse(response, processResponse)

    executePosts(request, response)
  }

  private def isConditionSuitable(request: FlowRequest, response: FlowResponse) : Boolean = {
    if(this.passCondition == null){
      return true
    }

    if(response.getLastResponse == null){
      return ExpressionEngine.getInstance.isTrue(this.passCondition, request.getParameters)
    }

    val responseValueMap = mutable.Map[String, Any]()
    responseValueMap += (ExpressionEngine.EXPECTING_PASS_INPUT_VARIABLE_NAME  -> response.getLastResponse.getResponse )
    ExpressionEngine.getInstance.isTrue(this.passCondition, responseValueMap.toMap)
  }

  private def executePosts(request: FlowRequest, response: FlowResponse) : Unit = {
    if(CollectionUtils.isEmpty(posts)){
      logger.info("This {} is one end of the flow  with route path {}", this.process.getKey, response.getRoutePath)
    }

    if(CollectionUtils.isEmpty(posts)){
      return;
    }

    var processedMainPost : ExtRoutedNode= null
    posts.foreach(route => {
      if(route.isMain){
        processedMainPost = executeMainPost(request, response, route, processedMainPost)
      }else{
        asynExecutePost(request, response, route)
      }
    })
  }
  private def executeMainPost(request: FlowRequest, response: FlowResponse, route : ExtRoutedNode, existingMainPost : ExtRoutedNode): ExtRoutedNode  ={
    if(existingMainPost != null){
      throwRequestUnsupportedException("The main post {} has been processed already!", existingMainPost)
    }
    executePost(request, response, route)
    route
  }

  private def executePost(request: FlowRequest, response: FlowResponse, route : ExtRoutedNode): Unit ={
    route.execute(request, response)
  }

  private def asynExecutePost(request: FlowRequest, response: FlowResponse, route : ExtRoutedNode): Unit ={
    val p = Promise[String]()
    Future {
      executePost(request.clone(), response.clone(), route)
      p success AsynUtils.FUTURE_STATUS_SUCCESS
    }(ExecutionContext.Implicits.global)
  }

  private def convertToProcessRequest(request: FlowRequest, response: FlowResponse, inputSchema : IOSchema) = {
    if(isConditionProcess){
      createProcessRequestForConditionProcess(request, response)
    }else{
      createProcessRequestForGeneralProcess(request, response, inputSchema)
    }
  }

  private def createProcessRequestForGeneralProcess(request: FlowRequest, response: FlowResponse, inputSchema : IOSchema) = {
    var processRequest: ProcessRequest = null
    if (this.mapper == null) {
      processRequest = AutoMatchUtils.extractProcessRequestByAutoMatch(request, response, inputSchema)
    } else {
      processRequest = this.mapper.map(request, response)
    }
    processRequest
  }

  private def createProcessRequestForConditionProcess(request: FlowRequest, response: FlowResponse) = {
    val processRequest: ProcessRequest = new ProcessRequest
    processRequest.addParameter(ConditionProcessConstant.EXPRESSION_EXPECTING_VARIABLE_REQUEST, request)
    processRequest.addParameter(ConditionProcessConstant.EXPRESSION_EXPECTING_VARIABLE_RESPONSE, response.getLastResponse)
    processRequest
  }

  private def isConditionProcess : Boolean = {
    if(this.process.getProcessType == ProcessType.CONDITION){
      return true
    }

    false
  }

  private def recordRequest(flowRequest: FlowRequest, processRequest: ProcessRequest): Unit ={
    flowRequest.addRequest(this.getProcess.getKey, processRequest)
  }

  private def recordResponse(flowResponse: FlowResponse, processResponse: ProcessResponse): Unit ={
    flowResponse.addResponse(this.getProcess.getKey, processResponse)
  }
}
